<?php
/**
 * @file model.class.php
 * @author 禅元天道 chanyuantiandao@126.com
 * @DateTime 2022-01-18 18:17
 * @brief TODO
 */

!defined('CHAN_CMS') && exit('非法访问！');

class Model
{
    private $sqlCmd = null;
    private static $tableInfo = null;
    private static $tableName = null;
    private $tempData = array();
    private $tempFields = '*';
    private $tempWhere = array();
    private $tempLimit = array();
    private $tempOrderBy = array();

    function __construct($table){
        $this->sqlCmd = App::db();
        if(self::$tableInfo == null){
            self::$tableName = $table;
            $result = $this->sqlCmd->getFieldsInfoOfTable($table);
            self::$tableInfo = array();
            foreach ($result as $field){
                self::$tableInfo[$field['name']] = $field;
            }
        }
    }

    function data($mArr){
        if(is_array($mArr) && !empty($mArr)){
            $allowedKeys = array_keys(self::$tableInfo);
            foreach ($mArr as $k => $v){
                if(!in_array($k, $allowedKeys)) {
                    throw new ChanException('数据校验的时候发现了不被允许的数据库字段: ' . $k . '。');
                }
                if(self::$tableInfo[$k]['type'] == 'int'){
                    if(getIntVal($v, null) === null){
                        throw new ChanException('数据校验的时候发现了非整数的值: ”' . $v . '”，而数据库要求类型为整数！');
                    }
                }else if(strpos(self::$tableInfo[$k]['type'], 'varchar') === 0){
                    $maxLen = substr(self::$tableInfo[$k]['type'], 8, -1);
                    if(mb_strlen($v) > $maxLen) {
                        throw new ChanException('数据校验的时候发现了超出长度(' . $maxLen . ')的值: “' . $v . '”，实际长度为：' . mb_strlen($v) . '。');
                    }
                }
            }
            $this->tempData = $mArr;
        }else{
            throw new ChanException('函数data()的参数校验未通过，错误：参数必须为数组！');
        }
        return $this;
    }

    function field($fields){
        $fields = is_array($fields) ? $fields : implode(',', $fields);
        $allowedKeys = array_keys(self::$tableInfo);
        foreach ($fields as $f) {
            if (!in_array($f, $allowedKeys)) {
                throw new ChanException('数据校验的时候发现了不存在的字段: ' . $f . '。');
            }
        }
        $this->tempFields = $fields;
        return $this;
    }

    function where(array $wheres){
        foreach ($wheres as $w){
            if(!is_array($w) || sizeof($w) != 3){
                throw new ChanException('查询条件必须是三元数组，例:$wheres = array(array(\'id\', 1, \'=\'), ...)。');
            }
        }
        $this->tempWhere = $wheres;
        return $this;
    }

    function order(array $orderBys){

        $allowedKeys = array_keys(self::$tableInfo);
        if(!is_array($orderBys)){
            throw new ChanException('排序条件必须是数组，例:$orderBys = array(\'id\'=>\'desc\', ...)。');
        }else{
            foreach ($orderBys as $f => $o)
            if (!in_array($f, $allowedKeys)) {
                throw new ChanException('数据校验的时候发现了不存在的字段: ' . $f . '。');
            }
            if (!in_array(strtolower($o), array('desc', 'asc'))) {
                throw new ChanException('排序方式仅限于升序（ASC）和降序（DESC）。');
            }
        }
        $this->tempOrderBy = $orderBys;
        return $this;
    }

    function limit(int $rowNum, int $startIndex = 0){
        if($rowNum <= 0){
            throw new ChanException('limit的参数不可以小于0。');
        }
        $startIndex = $startIndex < 0 ? 0 : $startIndex;
        $this->tempLimit = array($startIndex, $rowNum);
        return $this;
    }

    function add(){
        if(empty($this->tempData)){
            throw new ChanException('未设置要保存的模型数据！');
        }
        foreach (self::$tableInfo as $key => $val){
            if($val['isMust'] && !isset($this->tempData[$key]) && strpos($val['autoIncrement'], 'auto_increment') === false){
                throw new ChanException('数据库要求字段'.$key.'为非空字段，然而在添加数据时未提供该字段的值！');
            }
        }
        $result = $this->sqlCmd->insert(self::$tableName, $this->tempData);
        $this->resetData();
        if($result === true){
            App::removeCacheByFlag(getMd5(self::$tableName, 16), Cache::SQL);
            return $this->sqlCmd->getLastInsertId();
        }
        return false;
    }

    function update(){
        if(empty($this->tempData)){
            throw new ChanException('未设置要更新的模型数据！');
        }else if(empty($this->tempWhere)){
            throw new ChanException('未设置要更新的查询条件！');
        }
        $result = $this->sqlCmd->update(self::$tableName, $this->tempData, $this->tempWhere);
        $this->resetData();
        if($result === true){
            App::removeCacheByFlag(getMd5(self::$tableName, 16), Cache::SQL);
            return $this->sqlCmd->getAffectedRow();
        }else{
            return false;
        }
    }

    function fetch(){
        $key = getMd5(md5($this->tempFields).md5(serialize($this->tempWhere)).md5(serialize($this->tempOrderBy)).md5(serialize($this->tempLimit)), 16);
        $key = getMd5(self::$tableName, 16).$key;
        $cacheData = App::getCache($key, Cache::SQL);
        if($cacheData != null){
            $this->resetData();
            return unserialize($cacheData);
        }else{
            $result = $this->sqlCmd->select(self::$tableName, $this->tempFields, $this->tempWhere, $this->tempOrderBy, $this->tempLimit);
            $this->resetData();
            if($result === false){
                return false;
            }
            $data = $this->sqlCmd->fetchData();
            $cache = App::setCache($key, serialize($data), Cache::SQL);
            return $data;
        }
    }

    function remove(){
        if(empty($this->tempWhere)){
            throw new ChanException('未设置删除数据的查询条件！');
        }
        $result = $this->sqlCmd->delete(self::$tableName, $this->tempWhere);
        $this->resetData();
        if($result === true){
            App::removeCacheByFlag(getMd5(self::$tableName, 16), Cache::SQL);
        }
        return $result;
    }

    private function resetData(){
        $this->tempWhere = array();
        $this->tempLimit = array();
        $this->tempOrderBy = array();
        $this->tempFields = '*';
        $this->tempData = array();
    }
}
